﻿using System;
using System.Collections.Generic;
using STeaL.Algorithm;
using System.Diagnostics;

namespace STeaL.Collections.Generic {

  public class set_adaptor<T> : IList<T> {

    public set_adaptor(IList<T> list, Func<T,T,bool> pred) { 
      list_ = list; 
      pred_ = pred;
      list_.sort(pred_);
      int pos = list_.unique((x,y)=> !pred(x,y) && !pred(y,x));
      list_.erase(pos, list_.Count);
    }

    private IList<T> list_;
    private Func<T,T,bool> pred_;

    public int IndexOf(T item) { 
      int pos;
      return list_.binary_search(item, pred_, out pos) ? pos : -1;
    }

    public void Insert(int index, T item) {
      if ( (index > 0 && !pred_(list_[index-1],item)) ||
           (index < list_.Count && !pred_(item,list_[index])) ) {
        throw new ArgumentException("disordered insertion");
      }
      list_.Insert(index, item); 
      Debug.Assert(list_.is_sorted(pred_));
    }

    public void RemoveAt(int index) { list_.RemoveAt(index); }

    public T this[int index] { 
      get { return list_[index]; }
      set { if ( (index > 0 && !pred_(list_[index-1],value)) ||
                 (index < list_.Count-1 && !pred_(value, list_[index+1])) ) {
              throw new ArgumentException("disordered replacement");
            }
            list_[index] = value;
            Debug.Assert(list_.is_sorted(pred_));
          }
    }

    public void Add(T item) {
      int pos;
      if ( list_.binary_search(item,pred_,out pos) ) {
        throw new ArgumentException("item dupulicated");
      }
      list_.Insert(pos, item);
      Debug.Assert(list_.is_sorted(pred_));
    }

    public bool TryAdd(T item) {
      int pos;
      if ( list_.binary_search(item,pred_,out pos) ) {
        return false;
      }
      list_.Insert(pos, item);
      Debug.Assert(list_.is_sorted(pred_));
      return true;
    }

    public T AddOrReplace(T item) {
      T result;
      int pos;
      if ( list_.binary_search(item,pred_,out pos) ) {
        result = list_[pos];
        list_[pos] = item;
      } else {
        result = item;
        list_.Insert(pos, item);
      }
      Debug.Assert(list_.is_sorted(pred_));
      return result;
    }

    public void Clear() { list_.Clear(); }
    public bool Contains(T item) { return list_.binary_search(item,pred_); }
    public void CopyTo(T[] array, int arrayIndex) { list_.CopyTo(array, arrayIndex); }
    public int Count { get { return list_.Count;} }
    public bool IsReadOnly { get { return list_.IsReadOnly;} }

    public bool Remove(T item) {
      Tuple<int,int> range = list_.equal_range(item, pred_);
      list_.erase(range.Item1, range.Item2);
      return range.Item1 != range.Item2;
    }

    public IEnumerator<T> GetEnumerator() { return list_.GetEnumerator(); }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return list_.GetEnumerator(); }

#if STL_COMPATIBLE
    public Tuple<int,bool> insert(T item) {
      int pos = list_.lower_bound(item, pred_);
      bool inserted = false;
      if ( pred_(item, list_[pos]) ) {
        list_.insert(pos, item);
        inserted = true;
      }
      Debug.Assert(list_.is_sorted(pred_));
      return new Tuple<int,bool>(pos,inserted);
    }

    public int insert(int pos, T item) {
      if ( (pos > 0 && pred_(list_[pos-1],item)) &&
           (pos <= list_.Count && pred_(item,list_[pos])) ) {
        list_.Insert(pos,item);
      } else {
        pos = insert(item).Item1;
      } 
      Debug.Assert(list_.is_sorted(pred_));
      return pos;
    }

    public void insert(IEnumerable<T> items) {
      foreach ( T item in items ) insert(item);
    }

    public int erase(T item) {
      Tuple<int,int> range = list_.equal_range(item, pred_);
      list_.erase(range.Item1, range.Item2);
      return range.Item2 - range.Item1;
    }

    public int find(T item) {
      Tuple<int,int> range = list_.equal_range(item, pred_);
      return range.Item1 == range.Item2 ? list_.end() : range.Item2;
    }
#endif
      
  }

}
